﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AppLib.Modifiers;

public static class GlobalExtentions
{
    public static AsModifier<T> As<T>(this T self)
    {
        return new AsModifier<T> { Instance = self };
    }

    public static AsStringModifier As(this string self)
    {
        return new AsStringModifier { Instance = self };
    }

    public static AsEnumerableModifier<T> As<T>(this IEnumerable<T> self)
    {
        return new AsEnumerableModifier<T> { Instance = self };
    }

    public static IEnumerable<T> As<T>(this IEnumerable<AppEnvironment.Lib.Interfaces.IExportableTo<T>> self)
        where T : new()
    {
        if (self == null)
        {
            yield break;
        }
        else
        {
            foreach (var item in self)
            {
                T dest = new T();
                item.ExportTo(dest);
                yield return dest;
            }
        }
    }

    public static T As<T>(this AppEnvironment.Lib.Interfaces.IExportableTo<T> self)
        where T: new()
    {
        T dest = new T();
        if (self != null)
        {
            self.ExportTo(dest);
        }
        return dest;
    }

    public static Tuple<T1, T2> As<T1, T2>(this AppEnvironment.Lib.Interfaces.IExportableTo<T1, T2> self)
        where T1 : new()
        where T2 : new()
    {
        var dest1 = new T1();
        var dest2 = new T2();
        if (self != null)
        {
            self.ExportTo(dest1, dest2);
        }
        return new Tuple<T1, T2>(dest1, dest2);
    }

    public static Tuple<T1, T2, T3> As<T1, T2, T3>(this AppEnvironment.Lib.Interfaces.IExportableTo<T1, T2, T3> self)
        where T1 : new()
        where T2 : new()
        where T3 : new()
    {
        var dest1 = new T1();
        var dest2 = new T2();
        var dest3 = new T3();
        if (self != null)
        {
            self.ExportTo(dest1, dest2, dest3);
        }
        return new Tuple<T1, T2, T3>(dest1, dest2, dest3);
    }

    public static Tuple<T1, T2, T3, T4> As<T1, T2, T3, T4>(this AppEnvironment.Lib.Interfaces.IExportableTo<T1, T2, T3, T4> self)
        where T1 : new()
        where T2 : new()
        where T3 : new()
        where T4 : new()
    {
        var dest1 = new T1();
        var dest2 = new T2();
        var dest3 = new T3();
        var dest4 = new T4();
        if (self != null)
        {
            self.ExportTo(dest1, dest2, dest3, dest4);
        }
        return new Tuple<T1, T2, T3, T4>(dest1, dest2, dest3, dest4);
    }

    public static Tuple<T1, T2, T3, T4, T5> As<T1, T2, T3, T4, T5>(this AppEnvironment.Lib.Interfaces.IExportableTo<T1, T2, T3, T4, T5> self)
        where T1 : new()
        where T2 : new()
        where T3 : new()
        where T4 : new()
        where T5 : new()
    {
        var dest1 = new T1();
        var dest2 = new T2();
        var dest3 = new T3();
        var dest4 = new T4();
        var dest5 = new T5();
        if (self != null)
        {
            self.ExportTo(dest1, dest2, dest3, dest4, dest5);
        }
        return new Tuple<T1, T2, T3, T4, T5>(dest1, dest2, dest3, dest4, dest5);
    }


    static string[] TrueStrings = new string[] { "true", "yes" };
    static string[] FalseStrings = new string[] { "false", "no" };

    public static bool As(this string self, bool defaultIfEmpty)
    {
        string stringValue = (self ?? "").ToLower();
        if (TrueStrings.Contains(stringValue))
        {
            return (bool)(object)true;
        }
        else if (FalseStrings.Contains(stringValue))
        {
            return (bool)(object)false;
        }
        else
        {
            return defaultIfEmpty;
        }  
    }

    public static T As<T>(this object self, T defaultIfEmpty)
    {
        if (self.IsEmpty())
        {
            return defaultIfEmpty;
        }

        try
        {
            string stringValue = self as string;
            if (stringValue != null)
            {
                stringValue = (stringValue ?? "").Trim();                
                return (T)Convert.ChangeType(stringValue, typeof(T));
            }            
            return (T)Convert.ChangeType(self, typeof(T));
        }
        catch (Exception)
        {
            return defaultIfEmpty;
        }
    }

    public static T? As<T>(this string self)
        where T : struct
    {
        if (self.IsEmpty())
        {
            return new Nullable<T>();
        }

        try
        {
            string stringValue = self as string;
            if (stringValue != null)
            {
                stringValue = stringValue.Trim();
                return (T)Convert.ChangeType(stringValue, typeof(T));
            }
            return (T)Convert.ChangeType(self, typeof(T));
        }
        catch (Exception)
        {
            return new Nullable<T>();
        }
    }

    public static T DeserializedBinary<T>(this System.IO.Stream self)
        where T: new()
    {
        var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
        return (T)formatter.Deserialize(self);
    }

    internal static bool IsEmpty(this object value)
    {
        return ((value == null) || (value == System.DBNull.Value) ||
            ((value is string) && ((value as string) == string.Empty)));
    }
}

namespace AppLib.Modifiers
{
    public class AsModifier<T>
    {
        internal T Instance;

        /// <summary>
        /// checks if the instance variable is null.
        /// if null, returns new instance, else returns existing instance
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="self"></param>
        /// <returns></returns>
        public virtual T NotNull()
        {
            if (Instance == null)
            {
                return Activator.CreateInstance<T>();
            }
            else
            {
                return Instance;
            }
        }

        public virtual T NotNull(T defaultIfNull)
        {
            if (Instance == null)
            {
                return defaultIfNull;
            }
            else
            {
                return Instance;
            }
        }

        public virtual AsTestModifier<T> Test
        {
            get
            {
                return new AsTestModifier<T> { Instance = this.Instance };
            }
        }

        public virtual string SerializedXml()
        {
            T instance = NotNull();
            var serializer = new System.Xml.Serialization.XmlSerializer(instance.GetType());
            StringBuilder builder = new StringBuilder();
            var writer = System.Xml.XmlWriter.Create(builder, new System.Xml.XmlWriterSettings { OmitXmlDeclaration = true, Indent = true });

            var namespaces = new System.Xml.Serialization.XmlSerializerNamespaces();
            namespaces.Add("", "");

            serializer.Serialize(writer, instance, namespaces);
            return builder.ToString();
        }

        public virtual System.IO.Stream SerializedBinary()
        {
            T instance = NotNull();
            var formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            var stream = new System.IO.MemoryStream();
            formatter.Serialize(stream, instance);
            stream.Position = 0;
            return stream;
        }

        public TConvert? Nullable<TConvert>()
            where TConvert : struct
        {
            if (Instance.IsEmpty())
            {
                return new Nullable<TConvert>();
            }

            try
            {
                string stringValue = Instance as string;
                if (stringValue != null)
                {
                    stringValue = stringValue.Trim();
                    return (TConvert)Convert.ChangeType(stringValue, typeof(TConvert));
                }
                return (TConvert)Convert.ChangeType(Instance, typeof(TConvert));
            }
            catch (Exception)
            {
                return new Nullable<TConvert>();
            }
        }

        /// <summary>
        /// Converts the instance into the specified result using serialization and deserialization.
        /// Make sure both the input and output classes have the [Serialized] attribute, and both have the same names
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <returns></returns>
        public TResult SerialConvert<TResult>()
        {
            return NotNull().As().SerializedXml().As().DeserializedXml<TResult>();
        }
    }


    public class AsEnumerableModifier<T> : AsModifier<IEnumerable<T>>
    {
        public override IEnumerable<T> NotNull()
        {
            if (Instance == null)
            {
                return new T[] { };
            }
            else
            {
                return Instance;
            }
        }

        public override IEnumerable<T> NotNull(IEnumerable<T> defaultIfNull)
        {
            if (Instance == null)
            {
                return defaultIfNull;
            }
            else
            {
                return Instance;
            }
        }

        public IEnumerable<ToType> Enumerable<ToType>(ToType defaultIfNull)
        {
            foreach (var item in Instance)
            {
                yield return item.As<ToType>(defaultIfNull);
            }
        }
    }

    public class AsTestModifier<T>
    {
        internal T Instance;

        public virtual bool IsEmpty
        {
            get
            {
                return ((Instance == null)
                    || (Instance is DBNull)
                    );
            }
        }
    }

    public class AsStringTestModifier : AsTestModifier<string>
    {

        public override bool IsEmpty
        {
            get
            {
                return string.IsNullOrEmpty(Instance);
            }
        }

        /// <summary>
        /// Returns true if Email is in valid e-mail format.
        /// </summary>
        /// <param name="Email"></param>
        /// <returns></returns>
        public bool IsValidEmail()
        {
            if (String.IsNullOrEmpty(Instance))
                return false;
            return System.Text.RegularExpressions.Regex.
                IsMatch(Instance, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$");
        }
    }

    public class AsStringModifier : AsModifier<string>
    {
        /// <summary>
        /// checks if the instance variable is null.
        /// if null, returns empty string, else returns existing instance
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="self"></param>
        /// <returns></returns>
        public override string NotNull()
        {
            if (Instance == null)
            {
                return string.Empty;
            }
            else
            {
                return Instance;
            }
        }
        
        public override string NotNull(string defaultIfNull)
        {
            if (Instance == null)
            {
                return defaultIfNull;
            }
            else
            {
                return Instance;
            }
        }

        public new AsStringTestModifier Test
        {
            get
            {
                return new AsStringTestModifier { Instance = this.Instance };
            }
        }

        public DateTime? DateTime(params string[] acceptibleFormats)
        {
            DateTime result;
            if (System.DateTime.TryParseExact(NotNull(),
                acceptibleFormats,
                System.Globalization.CultureInfo.InvariantCulture,
                System.Globalization.DateTimeStyles.None,
                out result))
            {
                return result;
            }
            else
            {
                return null;
            }
        }

        public DateTime DateTime(DateTime defaultIfEmpty, params string[] acceptibleFormats)
        {
            return DateTime(acceptibleFormats) ?? defaultIfEmpty;
        }

        public TEnumType Enum<TEnumType>()
        {
            TEnumType defaultValue = default(TEnumType);
            if (!String.IsNullOrEmpty(Instance))
                try
                {
                    return (TEnumType)(System.Enum.Parse(typeof(TEnumType), Instance, true));
                }
                catch (ArgumentException)
                {
                    return defaultValue;
                }
            else
                return defaultValue;
        }

        public string MaxLength(int maxLength)
        {
            string value = Instance ?? "";
            if (value.Length > maxLength)
            {
                return value.Substring(0, maxLength);
            }
            else
            {
                return value;
            }
        }

        public T DeserializedXml<T>()
        {
            var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
            var reader = System.Xml.XmlReader.Create(new System.IO.StringReader(NotNull()));
            return (T)serializer.Deserialize(reader);
        }
    }
}